/*
 * Copyright (C) 2012 Tomas Shestakov. <http://code.google.com/p/jmmo/>
 */

package jmmo.engine.multifacetedactor

import org.scalatest.{BeforeAndAfterAll, WordSpec}
import akka.testkit.{TestActorRef, ImplicitSender, TestKit}
import akka.actor.{Props, ActorSystem}
import org.scalatest.matchers.ShouldMatchers

/**
 * Tests actor abilities to subscribe and unsubscribe to [[akka.event.EventStream]].
 * @author Tomas Shestakov
 * @param _system actor system for testing
 * @param creator a function which returns new instance of [[jmmo.engine.multifacetedactor.MultifacetedActor]]
 */
class ActorServicesSpec(_system: ActorSystem, creator: => MultifacetedActor with ActorServices, shutdown: Boolean = false)
  extends TestKit(_system) with ShouldMatchers with ImplicitSender with WordSpec with BeforeAndAfterAll {

  def this() = this(ActorSystem("TestSpec"), new MultifacetedActor with ActorServices {}, true)

  override def afterAll {
    if (shutdown) system.shutdown()
  }

  val subscriber = new ActorRole {
    def id = 'subscriber
    def receive = {
      case 'subscribe => context.system.eventStream.subscribe(testActor, classOf[ServiceEvent])
      case 'unsubscribe => context.system.eventStream.unsubscribe(testActor, classOf[ServiceEvent])
    }
  }

  val actor = TestActorRef[MultifacetedActor with ActorServices](Props({
    val newActor = creator
    newActor.addRole(subscriber)
    newActor
  }))

  val someService = system.actorOf(Props.empty)
  val otherService = system.actorOf(Props.empty)

  override val nestedSuites = {
    List(new MultifacetedActorSpec(system, creator))
  }

  "An ActorServices" should {

    "fit MultifacetedActorSpec" in {}

    "provide `services` method which returns empty map on actor has no services" in {
      actor.underlyingActor.services should be ('empty)
    }

    "provide `addService` method" which {

      "add service to `services` map" in {
        actor.underlyingActor.addService('service, someService, None)
        actor.underlyingActor.services should be (Map('service -> (someService, None)))
      }

      "does nothing on adding servise with id already exists in `services`" in {
        actor.underlyingActor.addService('service, otherService, Some("duplicated id"))
        actor.underlyingActor.services should be (Map('service -> (someService, None)))
      }
    }

    "provide `removeService` method" which {

      "does nothing on removing servise with id which not exists in `services`" in {
        actor.underlyingActor.removeService('srv)
        actor.underlyingActor.services should be (Map('service -> (someService, None)))
      }

      "remove service from `services` map" in {
        actor.underlyingActor.removeService('service)
        actor.underlyingActor.services should be ('empty)
      }
    }

    "handle `AddServiceRequest` message by call `addService` method and send `ServiceAvailable` to subscribers on `EventBus`" in {
      actor ! 'subscribe
      actor ! AddServiceRequest('service, someService, None)
      expectMsg(ServiceAvailable('service, someService, None))
      actor.underlyingActor.services should be (Map('service -> (someService, None)))
    }

    "ignore `AddServiceRequest` messages with service id already exists in `services`" in {
      actor ! AddServiceRequest('service, otherService, Some("duplicated id"))
      expectNoMsg()
      actor.underlyingActor.services should be (Map('service -> (someService, None)))
    }

    "handle `GetServicesRequest` message by send back `GetServicesResponse` message with content of `servises`" in {
      actor ! GetServicesRequest
      val servicesResponse = expectMsgType[GetServicesResponse]
      servicesResponse.services should be (Map('service -> (someService, None)))
    }

    "ignore `RemoveServiceRequest` messages with servise id does not exists in `services`" in {
      actor ! RemoveServiceRequest('srv)
      expectNoMsg()
      actor.underlyingActor.services should be (Map('service -> (someService, None)))
    }

    "handle `RemoveServiceRequest` messages by remove servise with specified id from `services`  and send `ServiceRevoked` to subscribers on `EventBus`" in {
      actor ! RemoveServiceRequest('service)
      expectMsg(ServiceRevoked('service, someService, None))
      actor.underlyingActor.services should be ('empty)
    }
  }

}
